﻿/*
Copyright © 2010 François Karman

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

See <license.txt> for the complete license of the software.
*/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Windows.Data;
using System.Windows.Input;

namespace Faz.Dependencies
{
    /// <summary>
    /// Defines the main presenter of the application.
    /// </summary>
    public class MainPresenter : AbstractPresenter
    {
        /// <summary>
        /// The command associated with the choose of a directory.
        /// </summary>
        private readonly ICommand chooseDirectory;

        /// <summary>
        /// The command associated with the sort order of the assemblies.
        /// </summary>
        private readonly ICommand sort;

        /// <summary>
        /// The command associated with the filtering of the assemblies.
        /// </summary>
        private readonly ICommand filter;

        /// <summary>
        /// The list of all the studied assemblies.
        /// </summary>
        private readonly ICollection<AssemblyOverview> assemblies;

        /// <summary>
        /// The current predicate used as a filter for the
        /// <see cref="MainPresenter.PreparedAssemblies"/> property.
        /// </summary>
        private FilterEventHandler currentFilter;

        /// <summary>
        /// The base directory where the analyzed assemblies are stored.
        /// </summary>
        private DirectoryInfo directory;

        /// <summary>
        /// Initializes a new instance of the MainPresenter class.
        /// </summary>
        public MainPresenter()
        {
            this.assemblies = new ObservableCollection<AssemblyOverview>();

            this.PreparedAssemblies = new CollectionViewSource();
            this.PreparedAssemblies.Source = this.Assemblies;

            this.chooseDirectory = new CommandPresenter(this.ExecuteChooseDirectory);
            if (System.IO.Directory.Exists(Properties.Settings.Default.DirectoryPath))
            {
                this.directory = new DirectoryInfo(Properties.Settings.Default.DirectoryPath);
            }

            this.sort = new CommandPresenter(this.ExecuteSort);
            this.filter = new CommandPresenter(this.ExecuteFilter);
        }

        /// <summary>
        /// Gets the command associated with the choose of a directory.
        /// </summary>
        public ICommand ChooseDirectory
        {
            get { return this.chooseDirectory; }
        }

        /// <summary>
        /// Gets the command associated with the sort order of the assemblies.
        /// </summary>
        public ICommand Sort
        {
            get { return this.sort; }
        }

        /// <summary>
        /// Gets the command associated with the filtering of the assemblies.
        /// </summary>
        public ICommand Filter
        {
            get { return this.filter; }
        }

        /// <summary>
        /// Gets the list of all the studied assemblies.
        /// </summary>
        public ICollection<AssemblyOverview> Assemblies
        {
            get { return this.assemblies; }
        }

        /// <summary>
        /// Gets the list of all the studied assemblies grouped and sorted.
        /// </summary>
        public CollectionViewSource PreparedAssemblies { get; private set; }

        /// <summary>
        /// Gets or sets the base directory where the analyzed assemblies are stored.
        /// </summary>
        public DirectoryInfo Directory
        {
            get
            {
                return this.directory;
            }

            set
            {
                if (this.directory != value)
                {
                    this.directory = value;

                    if (this.directory != null)
                    {
                        Properties.Settings.Default.DirectoryPath = this.directory.FullName;
                    }

                    this.RaisePropertyChanged("Directory");
                    this.UpdateAssemblies();
                }
            }
        }

        /// <summary>
        /// Opens a directory selector window to update the 
        /// <see cref="MainPresenter.Directory"/> property.
        /// </summary>
        public void ExecuteChooseDirectory()
        {
            System.Windows.Forms.FolderBrowserDialog dialog
                = new System.Windows.Forms.FolderBrowserDialog();

            if (this.Directory != null)
            {
                dialog.SelectedPath = this.Directory.FullName;
            }

            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                // TODO: Manage exceptions
                DirectoryInfo temp = new DirectoryInfo(dialog.SelectedPath);
                this.Directory = temp;
            }
        }

        /// <summary>
        /// Sorts the list of the displayed assemblies.
        /// </summary>
        /// <param name="parameter">
        /// The type of sort. Could one of the following string: Name, Level or Category.
        /// </param>
        public void ExecuteSort(object parameter)
        {
            if (parameter == null)
            {
                throw new ArgumentNullException("parameter");
            }

            string option = parameter as string;
            if (option == null)
            {
                throw new ArgumentException("parameter should be a string");
            }

            this.PreparedAssemblies.SortDescriptions.Clear();

            switch (option)
            {
                case "Name":
                    this.PreparedAssemblies.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
                    break;
                case "Level":
                    this.PreparedAssemblies.SortDescriptions.Add(new SortDescription("Level", ListSortDirection.Ascending));
                    break;
                case "Category":
                    this.PreparedAssemblies.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Descending));
                    break;
                default:
                    throw new ArgumentException("parameter should one of the following values: Name, Level or Category");
            }
        }

        /// <summary>
        /// Filters the list of the displayed assemblies.
        /// </summary>
        /// <param name="parameter">
        /// The type of filter. Could one of the following string: Initial, Indirect or All.
        /// </param>
        public void ExecuteFilter(object parameter)
        {
            if (parameter == null)
            {
                throw new ArgumentNullException("parameter");
            }

            string option = parameter as string;
            if (option == null)
            {
                throw new ArgumentException("parameter should be a string");
            }

            if (this.currentFilter != null)
            {
                this.PreparedAssemblies.Filter -= this.currentFilter;
            }

            switch (option)
            {
                case "Indirect":
                    this.currentFilter = (s, e) => e.Accepted = (e.Item as AssemblyOverview).Category != Category.Other;
                    break;
                case "Initial":
                    this.currentFilter = (s, e) => e.Accepted = (e.Item as AssemblyOverview).Category == Category.InitialAssembly;
                    break;
                case "All":
                    this.currentFilter = null;
                    break;
                default:
                    throw new ArgumentException("parameter should one of the following values: All, Indirect or Initial");
            }

            this.PreparedAssemblies.Filter += this.currentFilter;
        }

        /// <summary>
        /// Updates the <see cref="MainPresenter.Assemblies"/> collection based
        /// on the current selected directory.
        /// </summary>
        public void UpdateAssemblies()
        {
            if (this.Directory == null)
            {
                return;
            }

            // Clean existing values
            this.Assemblies.Clear();

            // Update Assemblies
            AssemblyOverviewLoader loader = new AssemblyOverviewLoader(this.Directory, this.Assemblies);
            loader.Extract();
        }
    }
}
